﻿#include "MqttWidget.h"
#include <qdatetime.h>
#include <QDebug>
#include <QStyledItemDelegate>
#include <qpainter.h>
#include <QFileDialog>
#include <QMessageBox>
#include <QMenu>
#include <QCompleter>
#include "AppConfigDB.h"
#include <QListWidget>
#include <QColumnView>
#include <QSplitter>

static const QRegExp rx("^(tcp|ssl|ws|wss)://[a-zA-Z0-9-.]+(:[0-9]{1,5})?$");

MqttWidget::MqttWidget(QWidget *parent) : QWidget(parent) //, client(nullptr)
{

	ui.setupUi(this);

	m_client = new QMqttClient(this);
	connect(m_client, &QMqttClient::connected, this, &MqttWidget::on_mqtt_connected);
	connect(m_client, &QMqttClient::disconnected, this, &MqttWidget::on_mqtt_disconnect);
	connect(m_client, &QMqttClient::messageReceived, this, &MqttWidget::on_mqtt_message_arrived);

	// right splitter stretch
	ui.right_splitter->setStretchFactor(0, 0);
	ui.right_splitter->setStretchFactor(1, 1);
	ui.right_splitter->setStretchFactor(2, 1);

	message_arrived_model = new QStandardItemModel(this);
	sub_topic_info_model = new QStandardItemModel(this);
	pub_topic_info_model = new QStandardItemModel(this);
	name_model = new QStandardItemModel(this);

	connect(sub_topic_info_model, &QStandardItemModel::itemChanged, this, &MqttWidget::on_sub_topic_item_changed);

	// 3:2 split
	ui.splitter->setStretchFactor(0, 3);
	ui.splitter->setStretchFactor(1, 2);

	// name_comboBox enable edit
	ui.name_comboBox->setEditable(true);
	connect(ui.name_comboBox, &QComboBox::editTextChanged, this, &MqttWidget::on_name_edit_text_changed);
	connect(ui.name_comboBox, &QComboBox::currentTextChanged, this, &MqttWidget::on_current_name_changed);

	// list widget as combo box popup
	QListView *listWidget = new QListView();
	// set model
	listWidget->setModel(name_model);
	// set combo box popup
	ui.name_comboBox->setModel(listWidget->model());
	ui.name_comboBox->setView(listWidget);

	// on name model row added
	connect(name_model, &QStandardItemModel::rowsInserted, this, &MqttWidget::on_name_model_inserted);

	QList<MqttInfoEntity> list = AppConfigDB::getInstance()->getMqttInfoList();
	for (int i = 0; i < list.size(); i++)
	{
		// add to model
		name_model->appendRow(new QStandardItem(list[i].name()));
	}

	// set message_arrived tableview model
	ui.message_arrived_tableView->setModel(message_arrived_model);
	// ui.message_arrived_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
	// set header name
	QStringList header_name_arrived = {"Topic", "Time", "Payload"};
	message_arrived_model->setHorizontalHeaderLabels(header_name_arrived);
	// time size to content
	ui.message_arrived_tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);

	// set sub_topic tableview model
	ui.subTopic_tableView->setModel(sub_topic_info_model);
	////set delegate for enable column
	// ui.subTopic_tableView->setItemDelegateForColumn(2, new CenteredCheckBoxDelegate(this));
	// double click to edit
	ui.subTopic_tableView->setEditTriggers(QAbstractItemView::DoubleClicked);

	// set header name
	QStringList header_name = {"Topic", "Qos", "Enable", "Operation"};
	sub_topic_info_model->setHorizontalHeaderLabels(header_name);

	// add context menu to sub topic table view when right click
	ui.subTopic_tableView->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui.subTopic_tableView, &QTableView::customContextMenuRequested, this, &MqttWidget::on_sub_topic_table_view_context_menu_requested);

	// set pub_topic tableview model
	ui.pub_topic_tableView->setModel(pub_topic_info_model);
	QStringList header_name_pub = {"Topic", "Qos", "Payload", "Description", "Operation"};
	pub_topic_info_model->setHorizontalHeaderLabels(header_name_pub);
	// disable edit
	ui.pub_topic_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);

	// host line edit change signal
	connect(ui.host_lineEdit, &QLineEdit::textChanged, this, &MqttWidget::on_host_edit_text_changed);

	// connect button click signal
	connect(ui.connect_pushButton, &QPushButton::clicked, this, &MqttWidget::on_connect_btn_clicked);

	// add sub topic button click signal
	connect(ui.add_subscribe_pushButton, &QPushButton::clicked, this, &MqttWidget::on_add_sub_topic_btn_clicked);

	// add send message plain text edit change signal
	connect(ui.payload_plainTextEdit, &QPlainTextEdit::textChanged, this, &MqttWidget::on_payload_plain_text_edit_changed);

	// add hex check box signal
	connect(ui.hex_checkBox, &QCheckBox::stateChanged, this, &MqttWidget::on_hex_checkbox_state_changed);

	// send message button click signal
	connect(ui.send_msg_pushButton, &QPushButton::clicked, this, &MqttWidget::on_send_msg_btn_clicked);

	// add clear button signal
	connect(ui.clear_pushButton, &QPushButton::clicked, this, &MqttWidget::on_clear_btn_clicked);

	// add export button signal
	connect(ui.export_pushButton, &QPushButton::clicked, this, &MqttWidget::on_export_btn_clicked);

	// connect(this, &MqttWidget::mqtt_connected, this, &MqttWidget::on_mqtt_connected);
	// connect(this, &MqttWidget::mqtt_connection_lost, this, &MqttWidget::on_mqtt_disconnect);
	// connect(this, &MqttWidget::mqtt_messageArrived, this, &MqttWidget::on_mqtt_message_arrived);

	connect(ui.add_pub_topic_pushButton, &QPushButton::clicked, this, &MqttWidget::on_add_pub_topic_btn_clicked);
	connect(ui.save_pub_topic_pushButton, &QPushButton::clicked, this, &MqttWidget::on_save_pub_topic_btn_clicked);
	connect(ui.cancel_pub_topic_pushButton, &QPushButton::clicked, this, &MqttWidget::on_cancel_pub_topic_btn_clicked);

	ui.pub_topic_stackedWidget->setCurrentIndex(0);

	resize(1600, 1200);
}

void MqttWidget::on_payload_plain_text_edit_changed()
{
	// check if is hex payload
	if (ui.hex_checkBox->isChecked())
	{
		// get payload
		auto payload = ui.payload_plainTextEdit->toPlainText();

		// remove space
		payload.remove(QRegExp("\\s"));

		// check if is valid hex
		if (payload.size() % 2 != 0 || !payload.contains(QRegExp("^[0-9a-fA-F]+$")))
		{
			ui.payload_plainTextEdit->setStyleSheet("border: 1px solid red");
			// disable send message button
			ui.send_msg_pushButton->setEnabled(false);
			return;
		}
		ui.payload_plainTextEdit->setStyleSheet("border: 1px solid green");
		// enable send message button
		ui.send_msg_pushButton->setEnabled(true);
		return;
	}
	ui.payload_plainTextEdit->setStyleSheet("border: 1px solid green");
}

void MqttWidget::on_hex_checkbox_state_changed()
{
	// check if is hex payload
	if (ui.hex_checkBox->isChecked())
	{
		// get payload
		auto payload = ui.payload_plainTextEdit->toPlainText();

		// remove space
		payload.remove(QRegExp("\\s"));

		// check if is valid hex
		if (payload.size() % 2 != 0 || !payload.contains(QRegExp("^[0-9a-fA-F]+$")))
		{
			ui.payload_plainTextEdit->setStyleSheet("border: 1px solid red");
			// disable send message button
			ui.send_msg_pushButton->setEnabled(false);
			return;
		}
		ui.payload_plainTextEdit->setStyleSheet("border: 1px solid green");
		// enable send message button
		ui.send_msg_pushButton->setEnabled(true);
		return;
	}
	ui.send_msg_pushButton->setEnabled(true);
	ui.payload_plainTextEdit->setStyleSheet("border: 1px solid green");
}

void MqttWidget::on_send_msg_btn_clicked()
{

	// check if is connect
	if (m_client->state() != QMqttClient::Connected)
	{
		return;
	}

	// check if is empty
	if (ui.topic_lineEdit->text().isEmpty() || ui.payload_plainTextEdit->toPlainText().isEmpty())
	{
		return;
	}

	// get topic
	auto topic = ui.topic_lineEdit->text();
	// get payload
	auto payload = ui.payload_plainTextEdit->toPlainText();

	auto qos = ui.qos_comboBox->currentText().toInt();
	auto retain = ui.retain_checkBox->isChecked();

	// mqtt::message_ptr pubmsg;

	QByteArray pubmsg;

	// check if is hex payload
	if (ui.hex_checkBox->isChecked())
	{
		// remove space
		payload.remove(QRegExp("\\s"));

		// convert to hex
		for (int i = 0; i < payload.size(); i += 2)
		{
			pubmsg.append(payload.mid(i, 2).toInt(nullptr, 16));
		}
	}
	else
	{
		pubmsg = payload.toUtf8();
	}

	// publish message
	// client->publish(pubmsg);
	m_client->publish(topic, pubmsg, qos, retain);
}

void MqttWidget::on_export_btn_clicked()
{
	// get file name
	QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "", tr("CSV Files (*.csv)"));
	if (fileName.isEmpty())
		return;
	// open file
	QFile file(fileName);
	if (!file.open(QIODevice::WriteOnly))
	{
		QMessageBox::information(this, tr("Unable to open file"), file.errorString());
		return;
	}
	// write data to file
	QTextStream out(&file);
	out << "Topic, Time, Payload\n";
	for (int i = 0; i < message_arrived_model->rowCount(); i++)
	{
		out << message_arrived_model->data(message_arrived_model->index(i, 0)).toString() << ","
			// << message_arrived_model->data(message_arrived_model->index(i, 1)).toString() << ","
			// time
			<< "'" + QDateTime::fromString(message_arrived_model->data(message_arrived_model->index(i, 1)).toString(), "yyyy-MM-dd hh:mm:ss.zzz").toString("yyyy-MM-dd hh:mm:ss.zzz") << ","
			<< message_arrived_model->data(message_arrived_model->index(i, 2)).toString() << "\n";
	}
	file.close();
}

void MqttWidget::on_delete_sub_topic_btn_clicked()
{

	// // get sender
	auto sender = qobject_cast<QPushButton *>(this->sender());

	QString topic;

	// find row in table view at column 3
	for (int i = 0; i < sub_topic_info_model->rowCount(); i++)
	{
		auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 3));
		// find delete button
		auto delete_button = widget->findChild<QPushButton *>("delete_button");
		if (delete_button == sender)
		{
			topic = sub_topic_info_model->item(i, 0)->text();
			// check if is enable
			auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 2));
			auto enable_check_box = widget->findChild<QCheckBox *>();
			if (enable_check_box->isChecked())
			{
				// client->unsubscribe(sub_topic_info_model->item(i, 0)->text().toStdString());
				m_client->unsubscribe(sub_topic_info_model->item(i, 0)->text());
			}

			// remove row from sqlite
			AppConfigDB::getInstance()->removeSubTopicInfo(get_sub_topic_info(i));

			sub_topic_info_model->removeRow(i);

			break;
		}
	}
}

void MqttWidget::on_sub_topic_item_hex_state_changed()
{
	// sender
	auto sender = qobject_cast<QCheckBox *>(this->sender());
	// find row in table view at column 3
	for (int i = 0; i < sub_topic_info_model->rowCount(); i++)
	{
		auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 3));
		// find hex check box
		auto hex_check_box = widget->findChild<QCheckBox *>();
		if (hex_check_box == sender)
		{
			// save sub topic info
			AppConfigDB::getInstance()->modifySubTopicInfo(get_sub_topic_info(i));
			return;
		}
	}
}

void MqttWidget::on_add_sub_topic_btn_clicked()
{

	add_sub_topic_info(SubTopicInfoEntity());
}

void MqttWidget::on_sub_topic_item_changed(QStandardItem *item)
{
	qDebug() << "on_sub_topic_item_changed: " << item->row() << " " << item->column();

	if (item->column() == 0)
	{
		if (item->text().isEmpty())
		{
			// item->setForeground(QBrush(QColor(255, 0, 0)));
			// disable enable check box
			auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(item->row(), 2));
			auto check_box = widget->findChild<QCheckBox *>();
			check_box->setEnabled(false);
			return;
		}
		// item->setForeground(QBrush(QColor(0, 0, 0)));
		// enable enable check box
		auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(item->row(), 2));
		auto check_box = widget->findChild<QCheckBox *>();
		check_box->setEnabled(true);
	}

	// modify sub topic info
	AppConfigDB::getInstance()->addOrUpdateSubTopicInfo(get_sub_topic_info(item->row()));
}

void MqttWidget::on_connect_btn_clicked()
{

	// check if is connect
	if (m_client->state() == QMqttClient::Connected)
	{
		// disconnect
		m_client->disconnectFromHost();
		return;
	}

	// disable connect button
	ui.connect_pushButton->setEnabled(false);

	// disable formLayout each line edit
	for (int i = 0; i < ui.formLayout->rowCount(); i++)
	{
		auto item = ui.formLayout->itemAt(i, QFormLayout::FieldRole);
		auto widget = item->widget();
		if (widget)
		{
			widget->setEnabled(false);
		}
	}

	qDebug() << "connect: " << ui.host_lineEdit->text() << ui.clientId_lineEdit->text();

	// parse url
	QUrl url(ui.host_lineEdit->text());

	// set host
	m_client->setHostname(url.host());
	// set port
	m_client->setPort(url.port());
	// set client id
	m_client->setClientId(ui.clientId_lineEdit->text());
	// set username
	m_client->setUsername(ui.username_lineEdit->text());
	// set password
	m_client->setPassword(ui.password_lineEdit->text());

	// set keep alive
	m_client->setKeepAlive(20);
	// set auto keep alive
	m_client->setAutoKeepAlive(true);
	// set protocol version
	m_client->setProtocolVersion(QMqttClient::ProtocolVersion::MQTT_3_1_1);

	// connect to host
	m_client->connectToHost();
}

void MqttWidget::on_host_edit_text_changed(const QString &text)
{
	if (rx.exactMatch(text))
	{
		ui.host_lineEdit->setStyleSheet("border: 1px solid green");

		// enable connect button
		ui.connect_pushButton->setEnabled(true && ui.name_comboBox->currentText().isEmpty() == false);
	}
	else
	{
		ui.host_lineEdit->setStyleSheet("border: 1px solid red");
		ui.connect_pushButton->setEnabled(false);
	}
}

void MqttWidget::on_name_edit_text_changed(const QString &text)
{
	qDebug() << "on_name_edit_text_changed: " << text;
	if (text.isEmpty())
	{
		ui.connect_pushButton->setEnabled(false);
	}
	else
	{
		ui.connect_pushButton->setEnabled(true && rx.exactMatch(ui.host_lineEdit->text()));
	}
}

void MqttWidget::on_name_model_inserted(const QModelIndex &parent, int first, int last)
{
	// add delete button
	QPushButton *delete_button = new QPushButton("X");
	// set max width
	delete_button->setMaximumWidth(60);

	auto horizontalLayout = new QHBoxLayout();
	// add spacer
	horizontalLayout->addStretch();
	horizontalLayout->addWidget(delete_button);
	horizontalLayout->setAlignment(Qt::AlignRight);
	horizontalLayout->setContentsMargins(0, 0, 0, 0);

	auto widget = new QWidget();
	widget->setLayout(horizontalLayout);

	auto listWidget = qobject_cast<QListView *>(ui.name_comboBox->view());

	// set index widget to list widget
	listWidget->setIndexWidget(name_model->index(last, 0), widget);

	// add signal
	connect(delete_button, &QPushButton::clicked, this, &MqttWidget::on_delete_name_btn_clicked);
}

void MqttWidget::on_delete_name_btn_clicked()
{
	// // get sender
	auto sender = qobject_cast<QPushButton *>(this->sender());
	// get row at name model
	for (int i = 0; i < name_model->rowCount(); i++)
	{
		auto widget = ui.name_comboBox->view()->indexWidget(name_model->index(i, 0));
		auto delete_button = widget->findChild<QPushButton *>();
		if (delete_button == sender)
		{
			// delete from sqlite
			AppConfigDB::getInstance()->deleteMqttInfoByName(name_model->item(i, 0)->text());
			// remove row
			name_model->removeRow(i);
			// refresh view
			ui.name_comboBox->showPopup();
			break;
		}
	}
}

void MqttWidget::on_current_name_changed(const QString &text)
{
	qDebug() << "on_current_name_changed: " << text;
	// query mqtt info
	MqttInfoEntity entity;
	if (AppConfigDB::getInstance()->getMqttInfoByName(text, entity))
	{
		ui.host_lineEdit->setText(entity.host());
		ui.clientId_lineEdit->setText(entity.clientId());
		ui.username_lineEdit->setText(entity.userName());
		ui.password_lineEdit->setText(entity.password());

		// enable connect button
		ui.connect_pushButton->setEnabled(true && rx.exactMatch(ui.host_lineEdit->text()));
	}
}

void MqttWidget::on_clear_btn_clicked()
{
	message_arrived_model->clear();
}

void MqttWidget::on_enable_sub_topic_item_changed()
{
	// sender
	auto sender = qobject_cast<QCheckBox *>(this->sender());
	// find row in table view at column 2
	for (int i = 0; i < sub_topic_info_model->rowCount(); i++)
	{
		auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 2));
		// find check box
		auto enable_check_box = widget->findChild<QCheckBox *>();
		if (enable_check_box == sender)
		{

			if (m_client->state() == QMqttClient::Connected)
			{
				if (enable_check_box->isChecked())
				{
					m_client->subscribe(sub_topic_info_model->item(i, 0)->text(), sub_topic_info_model->item(i, 1)->text().toInt());
				}
				else
				{
					m_client->unsubscribe(sub_topic_info_model->item(i, 0)->text());
				}
				set_sub_topic_row_editable(i, !enable_check_box->isChecked());
			}

			AppConfigDB::getInstance()->modifySubTopicInfo(get_sub_topic_info(i));

			return;
		}
	}
}

void MqttWidget::on_mqtt_connected()
{
	// qDebug() << "connected: " << cause;

	// enable connect button
	ui.connect_pushButton->setEnabled(true);

	ui.connect_pushButton->setText("Disconnect");

	// save mqtt info
	MqttInfoEntity entity;
	entity.setName(ui.name_comboBox->currentText().isEmpty() ? ui.host_lineEdit->text() : ui.name_comboBox->currentText());
	entity.setHost(ui.host_lineEdit->text());
	entity.setClientId(ui.clientId_lineEdit->text());
	entity.setUserName(ui.username_lineEdit->text());
	entity.setPassword(ui.password_lineEdit->text());

	// save to sqlite
	AppConfigDB::getInstance()->addOrUpdateMqttInfoByName(entity);

	// add to model
	if (name_model->findItems(entity.name()).size() == 0)
	{
		name_model->appendRow(new QStandardItem(entity.name()));
	}

	// enable subTopic_tableView
	ui.pub_topic_stackedWidget->setEnabled(true);
	ui.sub_topic_frame->setEnabled(true);
	ui.subTopic_tableView->setEnabled(true);

	// disable formLayout each line edit
	for (int i = 0; i < ui.formLayout->rowCount(); i++)
	{
		auto item = ui.formLayout->itemAt(i, QFormLayout::FieldRole);
		auto widget = item->widget();
		if (widget)
		{
			widget->setEnabled(false);
		}
	}

	// clear sub topic table view
	ui.subTopic_tableView->model()->removeRows(0, ui.subTopic_tableView->model()->rowCount());

	// // query sub topic info
	QList<SubTopicInfoEntity> list = AppConfigDB::getInstance()->getSubTopicInfoListByName(ui.name_comboBox->currentText());
	for (int i = 0; i < list.size(); i++)
	{
		add_sub_topic_info(list[i]);
	}

	// clear pub topic table view
	ui.pub_topic_tableView->model()->removeRows(0, ui.pub_topic_tableView->model()->rowCount());
	QList<PubTopicInfoEntity> pub_list = AppConfigDB::getInstance()->getPubTopicInfoListByName(ui.name_comboBox->currentText());
	for (int i = 0; i < pub_list.size(); i++)
	{
		add_pub_topic_info(pub_list[i]);
	}
}

void MqttWidget::on_mqtt_disconnect()
{
	// qDebug() << "connection_lost: " << cause;

	// enable connect button
	ui.connect_pushButton->setEnabled(true && ui.name_comboBox->currentText().isEmpty() == false);

	ui.connect_pushButton->setText("Connect");

	if (rx.exactMatch(ui.host_lineEdit->text()))
		ui.host_lineEdit->setStyleSheet("border: 1px solid green");
	else
		ui.host_lineEdit->setStyleSheet("border: 1px solid red");

	// disable subTopic_tableView
	ui.pub_topic_stackedWidget->setEnabled(false);
	ui.sub_topic_frame->setEnabled(false);

	// enable formLayout each line edit
	for (int i = 0; i < ui.formLayout->rowCount(); i++)
	{
		auto item = ui.formLayout->itemAt(i, QFormLayout::FieldRole);
		auto widget = item->widget();
		if (widget)
		{
			widget->setEnabled(true);
		}
	}

	ui.subTopic_tableView->model()->removeRows(0, ui.subTopic_tableView->model()->rowCount());

	ui.pub_topic_tableView->model()->removeRows(0, ui.pub_topic_tableView->model()->rowCount());
}

static bool check_topic_match(const QString &topic, const QString &wildcard)
{
	// check if is wildcard
	if (wildcard.contains('#') || wildcard.contains('+'))
	{
		// check if is valid wildcard
		if (wildcard.contains('#') && wildcard.indexOf('#') != wildcard.size() - 1)
		{
			return false;
		}
		if (wildcard.contains('+') && wildcard.indexOf('+') != wildcard.size() - 1)
		{
			return false;
		}

		// split topic and wildcard
		auto topic_list = topic.split('/');
		auto wildcard_list = wildcard.split('/');

		// check if size is not equal
		if (topic_list.size() != wildcard_list.size())
		{
			return false;
		}

		// check if is match
		for (int i = 0; i < topic_list.size(); i++)
		{
			if (wildcard_list[i] == "+")
			{
				continue;
			}
			if (wildcard_list[i] == "#")
			{
				return true;
			}
			if (topic_list[i] != wildcard_list[i])
			{
				return false;
			}
		}
		return true;
	}
	return topic == wildcard;
}

void MqttWidget::on_mqtt_message_arrived(const QByteArray &payload, const QMqttTopicName &topic)
{
	// qDebug() << "message_arrived: " << topic << " " << payload << " " << qos << " " << retained;

	// add data to model
	message_arrived_model->appendRow(new QStandardItem(topic.name()));
	auto row = message_arrived_model->rowCount() - 1;
	message_arrived_model->setItem(row, 1, new QStandardItem(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz")));

	bool is_hex = false;

	// if this topic is hex, convert payload to hex
	for (int i = 0; i < sub_topic_info_model->rowCount(); i++)
	{
		// if (sub_topic_info_model->item(i, 0)->text() == topic)
		// check if topic is match wildcard
		auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 2));
		auto enable_check_box = widget->findChild<QCheckBox *>();
		if (enable_check_box->isChecked() && check_topic_match(topic.name(), sub_topic_info_model->item(i, 0)->text()))
		{
			auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 3));
			auto hex_check_box = widget->findChild<QCheckBox *>();

			// enable check box
			widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 2));
			auto enable_check_box = widget->findChild<QCheckBox *>();

			if (hex_check_box->isChecked() && enable_check_box->isChecked())
			{
				is_hex = true;
				break;
			}
		}
	}

	QString display_payload = is_hex ? payload.toHex() : payload;

	message_arrived_model->setItem(row, 2, new QStandardItem(display_payload));
	// set tool tip to display_payload
	ui.message_arrived_tableView->model()->setData(ui.message_arrived_tableView->model()->index(row, 2), display_payload, Qt::ToolTipRole);

	// auto scroll to bottom
	if (ui.auto_scroll_checkBox->isChecked())
	{
		ui.message_arrived_tableView->scrollToBottom();
	}
}

void MqttWidget::on_sub_topic_table_view_context_menu_requested(const QPoint &pos)
{
	// get global position
	auto globalPos = ui.subTopic_tableView->mapToGlobal(pos);
	globalPos.setY(globalPos.y() + 20);
	// create menu
	QMenu myMenu;
	myMenu.addAction("add subscribe", this, &MqttWidget::on_add_sub_topic_btn_clicked);

	// show menu
	myMenu.exec(globalPos);
}

MqttWidget::~MqttWidget()
{
}

void MqttWidget::set_sub_topic_row_editable(int row, bool editable)
{
	// disable connect item changed signal
	disconnect(sub_topic_info_model, &QStandardItemModel::itemChanged, this, &MqttWidget::on_sub_topic_item_changed);
	if (editable)
	{
		sub_topic_info_model->item(row, 0)->setFlags(sub_topic_info_model->item(row, 0)->flags() | Qt::ItemIsEditable);
		sub_topic_info_model->item(row, 1)->setFlags(sub_topic_info_model->item(row, 1)->flags() | Qt::ItemIsEditable);
	}
	else
	{
		sub_topic_info_model->item(row, 0)->setFlags(sub_topic_info_model->item(row, 0)->flags() & ~Qt::ItemIsEditable);
		sub_topic_info_model->item(row, 1)->setFlags(sub_topic_info_model->item(row, 1)->flags() & ~Qt::ItemIsEditable);
	}
	// connect item changed signal
	connect(sub_topic_info_model, &QStandardItemModel::itemChanged, this, &MqttWidget::on_sub_topic_item_changed);

	save_sub_topic_info();
}

void MqttWidget::save_sub_topic_info()
{
	// save sub topic info
	QList<SubTopicInfoEntity> list;
	for (int i = 0; i < sub_topic_info_model->rowCount(); i++)
	{
		if (sub_topic_info_model->item(i, 0)->text().isEmpty())
		{
			continue;
		}
		SubTopicInfoEntity entity;
		entity.setName(ui.name_comboBox->currentText());
		entity.setTopic(sub_topic_info_model->item(i, 0)->text());
		entity.setQos(sub_topic_info_model->item(i, 1)->text().toInt());
		auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 2));
		auto check_box = widget->findChild<QCheckBox *>();
		entity.setEnable(check_box->isChecked());
		auto hex_widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(i, 3));
		auto hex_check_box = hex_widget->findChild<QCheckBox *>();
		entity.setHex(hex_check_box->isChecked());
		list.append(entity);
	}

	// save to sqlite
	AppConfigDB::getInstance()->saveSubTopicInfoListByName(ui.name_comboBox->currentText(), list);
}

void MqttWidget::add_sub_topic_info(const SubTopicInfoEntity &info)
{
	// standard item list
	QList<QStandardItem *> items;

	QStandardItem *topic_item = new QStandardItem(info.topic());
	items.append(topic_item);
	// qos item
	QStandardItem *qos_item = new QStandardItem(QString::number(info.qos()));
	items.append(qos_item);

	// add to model
	sub_topic_info_model->appendRow(items);

	auto row = sub_topic_info_model->rowCount() - 1;

	// resize column to content
	ui.subTopic_tableView->resizeColumnToContents(1);

	// enable check box widget
	QCheckBox *enable_check_box = new QCheckBox("Enable");
	// set max width
	enable_check_box->setMaximumWidth(100);
	enable_check_box->setEnabled(info.name().isEmpty() == false);
	// set check state
	enable_check_box->setChecked(info.enable());
	// add state changed signal
	connect(enable_check_box, &QCheckBox::stateChanged, this, &MqttWidget::on_enable_sub_topic_item_changed);

	// add item to table view
	auto widget = new QWidget();
	auto layout = new QHBoxLayout();
	layout->addWidget(enable_check_box);
	layout->setAlignment(Qt::AlignCenter);
	layout->setContentsMargins(4, 4, 4, 4);
	widget->setLayout(layout);
	ui.subTopic_tableView->setIndexWidget(sub_topic_info_model->index(row, 2), widget);

	// hex check box
	QCheckBox *hex_check_box = new QCheckBox("Hex");
	// set max width
	hex_check_box->setMaximumWidth(100);
	// set check state
	hex_check_box->setChecked(info.hex());
	// add state changed signal
	connect(hex_check_box, &QCheckBox::stateChanged, this, &MqttWidget::on_sub_topic_item_hex_state_changed);

	// add delete button
	QPushButton *delete_button = new QPushButton("Delete");
	// add item to button property
	delete_button->setObjectName("delete_button");

	//  set max width
	delete_button->setMaximumWidth(80);
	connect(delete_button, &QPushButton::clicked, this, &MqttWidget::on_delete_sub_topic_btn_clicked);

	// horizontal center button to cell
	layout = new QHBoxLayout();
	layout->addWidget(hex_check_box);
	layout->addWidget(delete_button);

	layout->setAlignment(Qt::AlignCenter);
	layout->setContentsMargins(4, 4, 4, 4);
	widget = new QWidget();
	widget->setLayout(layout);
	ui.subTopic_tableView->setIndexWidget(sub_topic_info_model->index(row, 3), widget);

	// if enable, subscribe
	if (info.enable())
	{
		m_client->subscribe(info.topic(), info.qos());
	}

	// set property to item
	sub_topic_info_model->item(row, 0)->setData(info.id(), Qt::UserRole);
}

void MqttWidget::add_or_update_pub_topic_info(PubTopicInfoEntity &info)
{
	auto id = info.id();
	// add to sqlite
	AppConfigDB::getInstance()->addOrUpdatePubTopicInfo(info);

	// update or add to model
	if (id == -1)
	{
		add_pub_topic_info(info);
	}
	else
	{
		// find row in table view
		for (int i = 0; i < pub_topic_info_model->rowCount(); i++)
		{
			if (pub_topic_info_model->item(i, 0)->data(Qt::UserRole).toInt() == info.id())
			{
				// update to model
				pub_topic_info_model->item(i, 0)->setText(info.topic());
				pub_topic_info_model->item(i, 1)->setText(QString::number(info.qos()));
				pub_topic_info_model->item(i, 2)->setText(info.payload());
				pub_topic_info_model->item(i, 3)->setText(info.description());
				// set property to item hex
				pub_topic_info_model->item(i, 3)->setData(info.hex(), Qt::UserRole);
				break;
			}
		}
	}

	// switch to view mode
	ui.pub_topic_stackedWidget->setCurrentIndex(0);
}

void MqttWidget::add_pub_topic_info(const PubTopicInfoEntity &info)
{
	QList<QStandardItem *> items;
	items.append(new QStandardItem(info.topic()));
	items.append(new QStandardItem(QString::number(info.qos())));
	items.append(new QStandardItem(info.payload()));
	items.append(new QStandardItem(info.description()));

	pub_topic_info_model->appendRow(items);

	auto row = pub_topic_info_model->rowCount() - 1;
	// set property to item
	pub_topic_info_model->item(row, 0)->setData(info.id(), Qt::UserRole);
	// set property to item hex
	pub_topic_info_model->item(row, 3)->setData(info.hex(), Qt::UserRole);

	// set size to content at column 1
	ui.pub_topic_tableView->resizeColumnToContents(1);
	// set tool tip
	ui.pub_topic_tableView->model()->setData(ui.pub_topic_tableView->model()->index(row, 0), info.topic(), Qt::ToolTipRole);
	ui.pub_topic_tableView->model()->setData(ui.pub_topic_tableView->model()->index(row, 2), info.payload(), Qt::ToolTipRole);
	ui.pub_topic_tableView->model()->setData(ui.pub_topic_tableView->model()->index(row, 3), info.description(), Qt::ToolTipRole);

	// operate column
	// select button
	QPushButton *select_button = new QPushButton("Select");
	// set max width
	select_button->setMaximumWidth(80);
	// add item to button property
	select_button->setObjectName("select_button");
	connect(select_button, &QPushButton::clicked, this, &MqttWidget::on_select_pub_topic_btn_clicked);
	// edit button
	QPushButton *edit_button = new QPushButton("Edit");
	// set max width
	edit_button->setMaximumWidth(80);
	// add item to button property
	edit_button->setObjectName("edit_button");
	connect(edit_button, &QPushButton::clicked, this, &MqttWidget::on_edit_pub_topic_btn_clicked);

	// delete button
	QPushButton *delete_button = new QPushButton("Delete");
	// add item to button property
	delete_button->setObjectName("delete_button");
	// set max width
	delete_button->setMaximumWidth(80);
	connect(delete_button, &QPushButton::clicked, this, &MqttWidget::on_delete_pub_topic_btn_clicked);

	// horizontal center button to cell
	auto layout = new QHBoxLayout();
	layout->addWidget(select_button);
	layout->addWidget(edit_button);
	layout->addWidget(delete_button);

	layout->setAlignment(Qt::AlignCenter);
	layout->setContentsMargins(4, 4, 4, 4);
	auto widget = new QWidget();
	widget->setLayout(layout);
	ui.pub_topic_tableView->setIndexWidget(pub_topic_info_model->index(row, 4), widget);
}

SubTopicInfoEntity MqttWidget::get_sub_topic_info(int row)
{
	SubTopicInfoEntity entity;
	entity.setId(sub_topic_info_model->item(row, 0)->data(Qt::UserRole).toInt());
	entity.setName(ui.name_comboBox->currentText());
	entity.setTopic(sub_topic_info_model->item(row, 0)->text());
	entity.setQos(sub_topic_info_model->item(row, 1)->text().toInt());
	auto widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(row, 2));
	auto check_box = widget->findChild<QCheckBox *>();
	entity.setEnable(check_box->isChecked());
	auto hex_widget = ui.subTopic_tableView->indexWidget(sub_topic_info_model->index(row, 3));
	auto hex_check_box = hex_widget->findChild<QCheckBox *>();
	entity.setHex(hex_check_box->isChecked());
	return entity;
}

PubTopicInfoEntity MqttWidget::get_pub_topic_info(int row)
{
	PubTopicInfoEntity entity;
	entity.setId(pub_topic_info_model->item(row, 0)->data(Qt::UserRole).toInt());
	entity.setName(ui.name_comboBox->currentText());
	entity.setTopic(pub_topic_info_model->item(row, 0)->text());
	entity.setQos(pub_topic_info_model->item(row, 1)->text().toInt());
	entity.setPayload(pub_topic_info_model->item(row, 2)->text());
	entity.setDescription(pub_topic_info_model->item(row, 3)->text());
	// set property to item hex
	entity.setHex(pub_topic_info_model->item(row, 3)->data(Qt::UserRole).toBool());

	return entity;
}

void MqttWidget::on_add_pub_topic_btn_clicked()
{
	// set pub_topic_stackedWidget property id to -1
	ui.pub_topic_stackedWidget->setProperty("id", -1);

	// clear line edit
	ui.pub_topic_lineEdit->clear();
	ui.pub_qos_comboBox->setCurrentText("0");
	ui.pub_payload_plainTextEdit->clear();
	ui.pub_describe_lineEdit->clear();
	// hex check box
	ui.pub_hex_checkBox->setChecked(false);

	// switch to edit mode
	ui.pub_topic_stackedWidget->setCurrentIndex(1);
}

void MqttWidget::on_save_pub_topic_btn_clicked()
{
	// // save pub topic info
	PubTopicInfoEntity entity;
	entity.setName(ui.name_comboBox->currentText());
	entity.setTopic(ui.pub_topic_lineEdit->text());
	entity.setQos(ui.pub_qos_comboBox->currentText().toInt());
	entity.setPayload(ui.pub_payload_plainTextEdit->toPlainText());
	entity.setDescription(ui.pub_describe_lineEdit->text());
	entity.setHex(ui.pub_hex_checkBox->isChecked());

	// get id
	auto id = ui.pub_topic_stackedWidget->property("id").toInt();
	entity.setId(id);

	add_or_update_pub_topic_info(entity);
}

void MqttWidget::on_cancel_pub_topic_btn_clicked()
{
	// // switch to view mode
	ui.pub_topic_stackedWidget->setCurrentIndex(0);
}

void MqttWidget::on_delete_pub_topic_btn_clicked()
{
	// // get sender
	auto sender = qobject_cast<QPushButton *>(this->sender());
	// get row at pub topic model
	for (int i = 0; i < pub_topic_info_model->rowCount(); i++)
	{
		auto widget = ui.pub_topic_tableView->indexWidget(pub_topic_info_model->index(i, 4));
		auto delete_button = widget->findChild<QPushButton *>("delete_button");
		if (delete_button == sender)
		{
			// delete from sqlite
			AppConfigDB::getInstance()->removePubTopicInfo(get_pub_topic_info(i));

			// remove row
			pub_topic_info_model->removeRow(i);
			break;
		}
	}
}

void MqttWidget::on_edit_pub_topic_btn_clicked()
{
	// // get sender
	auto sender = qobject_cast<QPushButton *>(this->sender());
	// get row at pub topic model
	for (int i = 0; i < pub_topic_info_model->rowCount(); i++)
	{
		auto widget = ui.pub_topic_tableView->indexWidget(pub_topic_info_model->index(i, 4));
		auto edit_button = widget->findChild<QPushButton *>("edit_button");
		if (edit_button == sender)
		{
			// set pub_topic_stackedWidget property id to id
			ui.pub_topic_stackedWidget->setProperty("id", pub_topic_info_model->item(i, 0)->data(Qt::UserRole).toInt());

			// set current index
			ui.pub_topic_lineEdit->setText(pub_topic_info_model->item(i, 0)->text());
			ui.pub_qos_comboBox->setCurrentText(pub_topic_info_model->item(i, 1)->text());
			ui.pub_payload_plainTextEdit->setPlainText(pub_topic_info_model->item(i, 2)->text());
			ui.pub_describe_lineEdit->setText(pub_topic_info_model->item(i, 3)->text());
			// hex check box
			ui.pub_hex_checkBox->setChecked(pub_topic_info_model->item(i, 3)->data(Qt::UserRole).toBool());

			// switch to edit mode
			ui.pub_topic_stackedWidget->setCurrentIndex(1);
			break;
		}
	}
}

void MqttWidget::on_select_pub_topic_btn_clicked()
{
	// // get sender
	auto sender = qobject_cast<QPushButton *>(this->sender());
	// get row at pub topic model
	for (int i = 0; i < pub_topic_info_model->rowCount(); i++)
	{
		auto widget = ui.pub_topic_tableView->indexWidget(pub_topic_info_model->index(i, 4));
		auto select_button = widget->findChild<QPushButton *>("select_button");
		if (select_button == sender)
		{
			ui.topic_lineEdit->setText(pub_topic_info_model->item(i, 0)->text());
			ui.qos_comboBox->setCurrentText(pub_topic_info_model->item(i, 1)->text());
			ui.payload_plainTextEdit->setPlainText(pub_topic_info_model->item(i, 2)->text());
			// hex check box
			ui.hex_checkBox->setChecked(pub_topic_info_model->item(i, 3)->data(Qt::UserRole).toBool());
			break;
		}
	}
}